// Copyright (C) 2010 and onwards Noam Shazeer and Georges Harik
// Pieces of this code may be licenced under the Apache License, version 2.0 
//   from Harik Shazeer Labs

#include "file.h"
#include <fstream>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

bool File::IsSharded(string name) { 
  return name.find('!') != string::npos; }

string File::Part0(string name) {
  return name.substr(0, name.find('!')); }

string File::Part1(string name) {
  return name.substr(name.find('!') + 1); }

uint64 File::NumShards(string p0) {
  ifstream in((p0 + "/meta").c_str());
  CHECK(in.is_open());
  uint64 n; in >> n; return n; }

string File::ShardFile(string name, uint64 shard) {
  return Part0(name) + "/" + itoa(shard) + "/" + Part1(name); }

// How many bytes in each shard, additional bytes in last shard
uint64 File::
ShardBytes(uint64 total_bytes, uint64 shard, uint64 num_shards) {
  // pages_per_shard  
  uint64 pps = total_bytes / (G_PAGE_SIZE * num_shards);
  uint64 bps = pps * G_PAGE_SIZE;
  if (shard == num_shards - 1)
    return total_bytes - (num_shards - 1) * bps;  
  return bps;
}

bool File::Exists(string name) {
  if (!IsSharded(name)) {
    struct stat st;
    int res = stat(name.c_str(), &st);
    if (res < 0) return false;
    return true;
  }
  return Exists(Part0(name));
}

uint64 File::Size(string name) {
  if (IsSharded(name)) {
    uint64 ret = 0;
    uint64 n = NumShards(Part0(name));
    for (uint64 i=0; i<n; i++) 
      ret += Size(ShardFile(name, i));
    return ret; }
  struct stat st;
  int res = stat(name.c_str(), &st);
  if (res < 0) {
    cout << "Error - could not open file " << name << endl;
    CHECK(false);
  }
  return st.st_size; 
}

void File::Write(string fn, Memblock data) {
  if (!IsSharded(fn)) {
    cout << "File::Write " << fn << endl;
    uint64 t0 = TimeNS();
    data.WriteToFile(fn); 
    uint64 t1 = TimeNS();
    uint64 size = Size(fn);
    cout << "size: " << size << " ";
    cout << "time: " << (t1 - t0) * 1e-9 << " ";
    cout << 1e3 * (double)size / (double)(t1 - t0) << " MB/s" << endl;
    return; }
  cout << "File::Write " << fn << endl;
  uint64 t0 = TimeNS();
  uint64 num_shards = NumShards(Part0(fn));
  uint64 start_byte = 0;
  JobQueue::Job job(G_JQ);
  for (uint64 shard = 0; shard < num_shards; shard++) {
    uint64 shard_bytes = ShardBytes(data.size(), shard, num_shards);
    job.AddTask
      (new WriteTask(ShardFile(fn, shard), 
		     Memblock(data.begin() + start_byte, shard_bytes)));
    start_byte += shard_bytes;
  }
  job.Wait();
  uint64 t1 = TimeNS();
  cout << "size: " << start_byte << " ";
  cout << "time: " << (t1 - t0) * 1e-9 << " ";
  cout << (double)start_byte / (double)(t1 - t0) << " MB/s" << endl;
}

void File::Read(string fn, Memblock data) {
  CHECK(Size(fn) == data.size());
  if (!IsSharded(fn)) {
    data.ReadFromFile(fn); return; }
  uint64 num_shards = NumShards(Part0(fn));
  uint64 start_byte = 0;
  JobQueue::Job job(G_JQ);
  for (uint64 shard = 0; shard < num_shards; shard++) {
    uint64 shard_bytes = ShardBytes(data.size(), shard, num_shards);
    if (!shard_bytes) continue;
    job.AddTask
      (new ReadTask(ShardFile(fn, shard), 
		    Memblock(data.begin() + start_byte, shard_bytes)));
    start_byte += shard_bytes;
  }
  CHECK(start_byte == data.size());
  job.Wait();
}

Memblock File::Read(string fn) {
  uint64 size = Size(fn);
  uint64 t0 = TimeNS();
  Memblock data(new char[size + 1], size);
  Read(fn, data);
  *(data.end()) = '\0';
  // uint64 chk = JQCheckSum(data);
  // cout << "chk: " << chk << endl;
  uint64 t1 = TimeNS();
  
  cout << "t: " << 1e-9 * (t1 - t0) << " "
       << "File::Read:: " << fn << " "
       << size << "B "
       << (double)size / (double)(t1 - t0) << " MB/s" << endl;
  return data;
}

void explore() {
  string cmd, f1, f2;
  while (1) {
    cin >> cmd;
    if (cmd == "cp") {
      cin >> f1 >> f2;
      cout << f1 << " -> " << f2 << endl;
      int64 t0 = TimeNS();
      Memblock m = File::Read(f1);
      File::Write(f2, m);
      int64 t1 = TimeNS();
      cout << 1e-9 * (t1 - t0) << endl;
    }
    if (cmd == "sz") {
      cin >> f1;
      uint64 size = File::Size(f1);
      cout << "size: " << size << endl;
    }
    if (cmd == "e") {
      cin >> f1;
      bool exists = File::Exists(f1);
      if (exists) 
	cout << "exists" << endl;
	else cout << "does not exist" << endl;
    }
    if (cmd == "q") return;
  }
}

int file_main() {
  InitJobQueue();
  explore();
  return 0;
}

// This is not really working
/*
Memblock File::Mmap(string fn) {
  cout << "File::Mmap " << fn << endl;
  if (!IsSharded(fn)) {
    Memblock ret = Mmap2(fn);
    cout << "size: " << ret.size_ << endl;
    uint64 chk = JQCheckSum(ret);
    cout << "chk: " << chk << endl;
    return ret;
  }
  uint64 n = NumShards(Part0(fn));
  // cout << "numshards:" << n << endl;
  char *start = NULL, *end = NULL;
  for (uint64 i=0; i<n; i++) {
    string sn = ShardFile(fn, i);
    // cout << "shardfile:" << sn << endl;
    uint64 size = Size(sn);
    // cout << "size:" << size << endl;
    if (i == (n-1)) size++;
    if (!size) continue;
    int fd = open(sn.c_str(), O_RDONLY);
    CHECK(fd != -1);
    char *ret = NULL;
    if (end) ret =
      (char*) mmap(end, size, PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0);
    else ret =
      (char*) mmap(end, size, PROT_READ, MAP_SHARED, fd, 0);
    // cout << "mmap returned " << (void*) ret << endl;
    CHECK(ret);
    if (end) {
      // cout << "ret:" << (void*) ret << " end:" << (void*) end << endl;
      CHECK(ret == end);
    }
    if (!start) start = ret;
    end = ret + size;
  }
  CHECK(end[-1] == '\0');
  Memblock ret = Memblock(start, end-start-1);
  cout << "size: " << ret.size_ << endl;
  uint64 chk = JQCheckSum(ret);
  cout << "chk: " << chk << endl;
  return ret;
}
*/
